/*
 * Copyright (C) 2012 United States Government as represented by the Administrator of the
 * National Aeronautics and Space Administration.
 * All Rights Reserved.
 */
package com.ou.csbs.capstone.gridlines;

import java.awt.Color;
import java.awt.Font;
import java.util.ArrayList;

import com.ou.csbs.capstone.views.worldwind.WorldWindCanvas;

import gov.nasa.worldwind.View;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.geom.Angle;
import gov.nasa.worldwind.geom.Extent;
import gov.nasa.worldwind.geom.Position;
import gov.nasa.worldwind.geom.Sector;
import gov.nasa.worldwind.geom.Vec4;
import gov.nasa.worldwind.globes.Globe;
import gov.nasa.worldwind.layers.GraticuleRenderingParams;
import gov.nasa.worldwind.render.DrawContext;
import gov.nasa.worldwind.render.GeographicText;
import gov.nasa.worldwind.render.Polyline;
import gov.nasa.worldwind.render.UserFacingText;

/**
 * The Extended UTM Graticule Layer object. 
 * 
 * When instanced, this class takes a UTMCoord object, and using zone identification, extends that zones boundaries out to
 * 0mE and 1,000,000mE. Due to time constraints, the min and max Northing values that are draw are 1,000,000mN in the southern 
 * hemisphere up to 9,000,000mN in the northern hemisphere.
 * 
 * @author Senior Capstone
 */

public class ExtendedUTMLayer extends UTMBaseGraticuleLayer{
	/** Graticule for the UTM LatBands. */
	public static final String GRATICULE_LATBAND = "Graticule.UTM.LatBand";
	/** Graticule for the 1,000,000 meter grid */
	public static final String GRATICULE_1000000M = "Graticule.1000000m";
	/** Graticule for the 100,000 meter grid */
	public static final String GRATICULE_100000M = "Graticule.100000m";
	/** Graticule for the 10,000 meter grid */
	public static final String GRATICULE_10000M = "Graticule.10000m";
	/** Graticule for the 1,000 meter grid */
	public static final String GRATICULE_1000M = "Graticule.1000m";
	/** Graticule for the 100 meter grid */
	public static final String GRATICULE_100M = "Graticule.100m";
	/** Graticule for the 10 meter grid */
	public static final String GRATICULE_10M = "Graticule.10m";
	/** Graticule for the 1 meter grid */
	public static final String GRATICULE_1M = "Graticule.1m";

	/** Holds the square tiles for each of the meter grids */
	protected ArrayList<ExtendedTile> extendedTiles = new ArrayList<ExtendedTile>();
	/** Holds tiles specifically for identifying Latitude bands in UTM */
	protected ArrayList<LatBandTile> latBandTiles = new ArrayList<LatBandTile>();
	/** The reference coordinate that resides in the extended UTM zone */
	private UTMCoord referenceCoord;
	
	protected static final int MIN_CELL_SIZE_PIXELS = 10;

	public ExtendedUTMLayer(){
		initRenderingParams();
		this.metricScaleSupport.setMaxResolution(1e6);
		this.setPickEnabled(false);
		this.setName("ExtendedUTMLayer");
		this.setDrawLabels(true);
		setCoordInZone(UTMCoord.fromLatLon(Angle.ZERO, Angle.ZERO));
		setExtendedZone(referenceCoord.getZone());
	}

	public UTMCoord getReferenceCoord(){
		return this.referenceCoord;
	}

	public void setCoordInZone(UTMCoord coordInZone) {
		this.referenceCoord = UTMCoord.fromLatLon(Angle.ZERO, coordInZone.getCentralMeridian());
		setExtendedZone(referenceCoord.getZone());
		for(ExtendedTile et : this.extendedTiles)
			et.clearRenderables();
		for(LatBandTile lbt : this.latBandTiles)
			lbt.clearRenderables();
		this.extendedTiles.clear();
		this.latBandTiles.clear();
		createTiles();
		this.firePropertyChange(AVKey.LAYER, null, this);
	}

	private void createTiles(){
		int step = 1000000;
		long startNorthing = 10000000;
		Globe globe = WorldWindCanvas.getWorldWindModel().getGlobe();
		UTMCoord topLeft, bottomLeft, topRight, bottomRight;

		//create north hemisphere tiles
		for(long northings = startNorthing - step; northings > 0; northings -= step){
			topLeft = UTMCoord.fromUTM(this.referenceCoord.getZone(), AVKey.NORTH, 0, northings, globe);
			topRight = UTMCoord.fromUTM(this.referenceCoord.getZone(), AVKey.NORTH, 1000000, northings, globe);
			bottomLeft = UTMCoord.fromUTM(this.referenceCoord.getZone(), AVKey.NORTH, 0, northings-step, globe);
			bottomRight = UTMCoord.fromUTM(this.referenceCoord.getZone(), AVKey.NORTH, 1000000, northings-step, globe);

			this.extendedTiles.add(new ExtendedTile(topLeft, topRight, bottomLeft, bottomRight, 1000000));
		}
		//create south hemisphere tiles
		for(long northings = startNorthing; northings > step; northings -= step){
			topLeft = UTMCoord.fromUTM(this.referenceCoord.getZone(), AVKey.SOUTH, 0, northings, globe);
			topRight = UTMCoord.fromUTM(this.referenceCoord.getZone(), AVKey.SOUTH, 1000000, northings, globe);
			bottomLeft = UTMCoord.fromUTM(this.referenceCoord.getZone(), AVKey.SOUTH, 0, northings-step, globe);
			bottomRight = UTMCoord.fromUTM(this.referenceCoord.getZone(), AVKey.SOUTH, 1000000, northings-step, globe);

			this.extendedTiles.add(new ExtendedTile(topLeft, topRight, bottomLeft, bottomRight, 1000000));
		}
		//create lat band tiles
		for(int i = 80; i > -80; i-=8){
			if(i == 80)
				this.latBandTiles.add(new LatBandTile(84, 72));
			else
				this.latBandTiles.add(new LatBandTile(i, i-8));
		}
	}

	protected void selectRenderables(DrawContext dc){
		ArrayList<ExtendedTile> tileList = getVisibleTiles(dc);
		if(tileList.size() > 0){
			for(ExtendedTile et : tileList){
				et.selectRenderables(dc);
			}
		}
		for(LatBandTile lbt : latBandTiles){
			lbt.selectRenderables(dc);
		}

		if(!(dc.getVisibleSector().getMinLatitude().getDegrees() > 84 && dc.getVisibleSector().getMaxLatitude().getDegrees() < -80))
			this.metricScaleSupport.selectRenderables(dc);
	}
	
	protected ArrayList<ExtendedTile> getVisibleTiles(DrawContext dc){
		ArrayList<ExtendedTile> tileList = new ArrayList<ExtendedTile>();
		for(ExtendedTile et : this.extendedTiles){
			if(et.isInView(dc))
				tileList.add(et);
			else
				et.clearRenderables();
		}
		return tileList;
	}

	protected void initRenderingParams(){
		GraticuleRenderingParams params;
		//LatBands
		params = new GraticuleRenderingParams();
		params.setValue(GraticuleRenderingParams.KEY_LINE_COLOR, Color.WHITE);
		params.setValue(GraticuleRenderingParams.KEY_LABEL_COLOR, Color.WHITE);
		params.setValue(GraticuleRenderingParams.KEY_LABEL_FONT, Font.decode("Arial-Bold-16"));
		setRenderingParams(GRATICULE_LATBAND, params);
		// 1,000,000 meter graticule
		params = new GraticuleRenderingParams();
		params.setValue(GraticuleRenderingParams.KEY_LINE_COLOR, Color.YELLOW);
		params.setValue(GraticuleRenderingParams.KEY_LABEL_COLOR, Color.YELLOW);
		params.setValue(GraticuleRenderingParams.KEY_LABEL_FONT, Font.decode("Arial-Bold-16"));
		setRenderingParams(GRATICULE_1000000M, params);
		// 100,000 meter graticule
		params = new GraticuleRenderingParams();
		params.setValue(GraticuleRenderingParams.KEY_LINE_COLOR, Color.GREEN);
		params.setValue(GraticuleRenderingParams.KEY_LABEL_COLOR, Color.GREEN);
		setRenderingParams(GRATICULE_100000M, params);
		// 10,000 meter graticule
		params = new GraticuleRenderingParams();
		params.setValue(GraticuleRenderingParams.KEY_LINE_COLOR, new Color(0, 102, 255));
		params.setValue(GraticuleRenderingParams.KEY_LABEL_COLOR, new Color(0, 102, 255));
		setRenderingParams(GRATICULE_10000M, params);
		// 1,000 meter graticule
		params = new GraticuleRenderingParams();
		params.setValue(GraticuleRenderingParams.KEY_LINE_COLOR, Color.CYAN);
		params.setValue(GraticuleRenderingParams.KEY_LABEL_COLOR, Color.CYAN);
		setRenderingParams(GRATICULE_1000M, params);
		// 100 meter graticule
		params = new GraticuleRenderingParams();
		params.setValue(GraticuleRenderingParams.KEY_LINE_COLOR, new Color(0, 153, 153));
		params.setValue(GraticuleRenderingParams.KEY_LABEL_COLOR, new Color(0, 153, 153));
		setRenderingParams(GRATICULE_100M, params);
		// 10 meter graticule
		params = new GraticuleRenderingParams();
		params.setValue(GraticuleRenderingParams.KEY_LINE_COLOR, new Color(102, 255, 204));
		params.setValue(GraticuleRenderingParams.KEY_LABEL_COLOR, new Color(102, 255, 204));
		setRenderingParams(GRATICULE_10M, params);
		// 1 meter graticule
		params = new GraticuleRenderingParams();
		params.setValue(GraticuleRenderingParams.KEY_LINE_COLOR, new Color(153, 153, 255));
		params.setValue(GraticuleRenderingParams.KEY_LABEL_COLOR, new Color(153, 153, 255));
		setRenderingParams(GRATICULE_1M, params);
	}

	protected String[] getOrderedTypes(){
		return new String[] {
				GRATICULE_LATBAND,
				GRATICULE_1000000M,
				GRATICULE_100000M,
				GRATICULE_10000M,
				GRATICULE_1000M,
				GRATICULE_100M,
				GRATICULE_10M,
				GRATICULE_1M,
		};
	}

	protected String getTypeFor(int resolution){
		String graticuleType = null;
		switch (resolution){
		case 1000000: // 1,000,000 meters
			graticuleType = GRATICULE_1000000M;
			break;
		case 100000: // 100,000 meters
			graticuleType = GRATICULE_100000M;
			break;
		case 10000:  // 10,000 meters
			graticuleType = GRATICULE_10000M;
			break;
		case 1000:   // 1000 meters
			graticuleType = GRATICULE_1000M;
			break;
		case 100:    // 100 meters
			graticuleType = GRATICULE_100M;
			break;
		case 10:     // 10 meters
			graticuleType = GRATICULE_10M;
			break;
		case 1:      // 1 meter
			graticuleType = GRATICULE_1M;
			break;
		}

		return graticuleType;
	}

	private void applyTerrainConformance(){
		String[] graticuleType = getOrderedTypes();
		for (String type : graticuleType){
			getRenderingParams(type).setValue(GraticuleRenderingParams.KEY_LINE_CONFORMANCE, this.polylineTerrainConformance);
		}
	}

	protected Sector computeVisibleSector(DrawContext dc){
		return dc.getVisibleSector();
	}

	protected void clear(DrawContext dc){
		super.clear(dc);
		this.metricScaleSupport.clear();
		this.metricScaleSupport.setZone(referenceCoord.getZone());
		this.applyTerrainConformance();
	}

	/**
	 * Extended UTM Graticule Tile
	 * 
	 * This tile represents square areas in meters. The resolution identifies the length and width in meters of the square area.
	 * 
	 * Given that a square area on the surface of earth is not of fixed Latitude and Longitude, the corners of the square area must be
	 * kept in order to perform calculations and to convert to LatLon objects for drawing.
	 * 
	 * @author Senior Capstone
	 */
	class ExtendedTile{
		private ArrayList<GridElement> gridElements;
		private ArrayList<ExtendedTile> subTiles;
		int resolution;
		UTMCoord topLeft, topRight, bottomLeft, bottomRight;
		private Sector mainSector;
		/**
		 * Given that some extended zones may end up crossing the longitude date line (+/-180 degrees) and the Sector class does not perform
		 * shortest path calculations, a tile may need two separate Sectors to identify if it is in view or not. If the tile does not cross 
		 * the longitude date line, it is left null and all Sector operations are preformed on mainSector.
		 */
		private Sector crossDateLineSector;

		public ExtendedTile(UTMCoord topLeft, UTMCoord topRight, UTMCoord bottomLeft, UTMCoord bottomRight, int resolution){
			this.topLeft = topLeft;
			this.topRight = topRight;
			this.bottomLeft = bottomLeft;
			this.bottomRight = bottomRight;

			this.resolution = resolution;

			Sector sector = Sector.fromUTMRectangle(referenceCoord.getZone(), 
					bottomLeft.getHemisphere(), 
					bottomLeft.getEasting(), 
					bottomRight.getEasting(), 
					bottomLeft.getNorthing(), 
					topLeft.getNorthing());
			this.mainSector = new Sector(sector);

			//this is for handling sectors that cross the date line
			if(sector.getMinLongitude().getDegrees() < -150 && sector.getMaxLongitude().getDegrees() > 150){
				this.mainSector = new Sector(sector.getMinLatitude(), 
						sector.getMaxLatitude(),
						sector.getMaxLongitude(),
						Angle.fromDegrees(180));
				this.crossDateLineSector = new Sector(sector.getMinLatitude(),
						sector.getMaxLatitude(),
						Angle.fromDegrees(-180),
						sector.getMinLongitude());
			}
		}

		public void clearRenderables(){
			if(this.gridElements != null){
				this.gridElements.clear();
				this.gridElements = null;
			}
			if(this.subTiles != null){
				for(ExtendedTile et : this.subTiles){
					et.clearRenderables();
				}
				this.subTiles.clear();
				this.subTiles = null;
			}
		}

		public Extent getExtent(Globe globe, double ve){
			double centerElevation = globe.getElevation(this.mainSector.getCentroid().getLatitude(), this.mainSector.getCentroid().getLongitude());
			return Sector.computeBoundingCylinder(globe, ve, this.mainSector, -5000, centerElevation);
		}

		public Extent getDateLineExtent(Globe globe, double ve){
			double centerElevation = globe.getElevation(this.crossDateLineSector.getCentroid().getLatitude(), this.crossDateLineSector.getCentroid().getLongitude());
			return Sector.computeBoundingCylinder(globe, ve, this.crossDateLineSector, -5000, centerElevation);
		}

		public boolean isInView(DrawContext dc){
			//this is for handling frustum intersections with sectors that cross the date line
			if(!viewFrustum.intersects(this.getExtent(dc.getGlobe(), dc.getVerticalExaggeration()))){
				if(this.crossDateLineSector != null && viewFrustum.intersects(this.getDateLineExtent(dc.getGlobe(), dc.getVerticalExaggeration())))
					return true;
				else
					return false;
			}
			// Check apparent size
			if (getSizeInPixels(dc) / 10 < MIN_CELL_SIZE_PIXELS)
				return false;

			if(this.resolution == 10)
				return false;

			return true;
		}

		public void selectGridElement(DrawContext dc, GridElement ge, boolean drawMetricLabels, String graticuleType, int step){

			if(!(dc.getVisibleSector().getMinLatitude().getDegrees() > 84 && dc.getVisibleSector().getMaxLatitude().getDegrees() < -80) && drawMetricLabels)
				metricScaleSupport.computeMetricScaleExtremes(referenceCoord.getZone(), this.bottomLeft.getHemisphere(), ge, this.resolution*10);

			if(ge.type.equals(GridElement.TYPE_LINE_SOUTH)){
				if(resolution < 1000000 && (this.bottomLeft.getNorthing() != 0 && this.bottomLeft.getNorthing() % step != 0))
					addRenderable(ge.renderable, graticuleType);
			}
			else{
				if(resolution < 1000000 && this.bottomLeft.getEasting() != 0 && this.bottomLeft.getEasting() % step != 0)
					addRenderable(ge.renderable, graticuleType);
			}
		}

		public void selectRenderables(DrawContext dc){
			if(this.gridElements == null)
				createRenderables(dc);
			boolean drawMetricLabels = getSizeInPixels(dc) > MIN_CELL_SIZE_PIXELS * 2;
			String graticuleType = getTypeFor(resolution);
			int step = this.resolution*10;
			for (GridElement ge : this.gridElements){
				if(ge.type.equals(GridElement.TYPE_LINE_SOUTH) || ge.type.equals(GridElement.TYPE_LINE_WEST)){
					if(resolution == 1000000 && ge.type.equals(GridElement.TYPE_LINE_SOUTH)){
						addRenderable(ge.renderable, graticuleType);
					}
					if (ge.isInView(dc)){
						selectGridElement(dc, ge, drawMetricLabels, graticuleType, step);
					}
				}
			}

			if (getSizeInPixels(dc) / 10 < MIN_CELL_SIZE_PIXELS * 2)
				return;

			if(this.subTiles == null)
				createSubTiles();
			for(ExtendedTile et : this.subTiles){
				if(et.isInView(dc))
					et.selectRenderables(dc);
				else
					et.clearRenderables();
			}
		}
		public double getSizeInPixels(DrawContext dc){
			View view = dc.getView();
			Vec4 centerPoint = getSurfacePoint(dc, this.mainSector.getCentroid().getLatitude(),
					this.mainSector.getCentroid().getLongitude());
			double distance = view.getEyePoint().distanceTo3(centerPoint);
			double tileSizeMeter = this.mainSector.getDeltaLatRadians() * dc.getGlobe().getRadius();
			return tileSizeMeter / view.computePixelSizeAtDistance(distance);
		}

		private void createSubTiles(){
			this.subTiles = new ArrayList<ExtendedTile>();

			int step = this.resolution / 10;
			Globe globe = WorldWindCanvas.getWorldWindModel().getGlobe();
			double startNorthing = this.topLeft.getNorthing();
			double endNorthing = this.bottomLeft.getNorthing();
			double startEasting = this.topLeft.getEasting();
			double endEasting = this.topRight.getEasting();
			UTMCoord topLeft, bottomLeft, topRight, bottomRight;
			String hemisphere = this.topLeft.getHemisphere();

			for(double northings = startNorthing; northings > endNorthing; northings -= step){
				for(double eastings = startEasting; eastings < endEasting; eastings += step){
					topLeft = UTMCoord.fromUTM(referenceCoord.getZone(), hemisphere, eastings, northings, globe);
					topRight = UTMCoord.fromUTM(referenceCoord.getZone(), hemisphere, eastings+step, northings, globe);
					bottomLeft = UTMCoord.fromUTM(referenceCoord.getZone(), hemisphere, eastings, northings-step, globe);
					bottomRight = UTMCoord.fromUTM(referenceCoord.getZone(), hemisphere, eastings+step, northings-step, globe);

					this.subTiles.add(new ExtendedTile(topLeft, topRight, bottomLeft, bottomRight, step));
				}
			}
		}
		private ArrayList<Position> UTMListToPositionList(Iterable<? extends UTMCoord> coords){
			ArrayList<Position> ret = new ArrayList<Position>();
			for(UTMCoord utm : coords){
				ret.add(Position.fromDegrees(utm.getLatitude().getDegrees(), utm.getLongitude().getDegrees()));
			}
			return ret;
		}
		private void createRenderables(DrawContext dc){
			this.gridElements = new ArrayList<GridElement>();
			ArrayList<UTMCoord> coords = new ArrayList<UTMCoord>();
			ArrayList<Position> positions = new ArrayList<Position>();
			Globe globe = WorldWindCanvas.getWorldWindModel().getGlobe();

			int step = this.resolution/10;
			if(this.resolution > 100000)
				step = step / 10;	//a little extra accuracy for larger scale lines
			// left meridian segment
			positions.clear();
			coords.clear();
			for(double northings = this.topLeft.getNorthing(); northings >= this.bottomLeft.getNorthing(); northings-=step){
				coords.add(UTMCoord.fromUTM(referenceCoord.getZone(), this.topLeft.getHemisphere(), this.topLeft.getEasting(), northings, globe));
			}
			positions = UTMListToPositionList(coords);
			Object polyline = createLineRenderable(positions, Polyline.LINEAR);
			Sector lineSector = Sector.fromDegrees(this.bottomLeft.getLatitude().getDegrees(), this.topLeft.getLatitude().getDegrees(), this.topLeft.getLongitude().getDegrees(), this.bottomLeft.getLongitude().getDegrees());
			GridElement west = new GridElement(lineSector, polyline, GridElement.TYPE_LINE_WEST);
			west.value = this.bottomLeft.getEasting();
			this.gridElements.add(west);

			// right meridian segment
			positions.clear();
			coords.clear();
			for(double northings = this.topRight.getNorthing(); northings >= this.bottomRight.getNorthing(); northings-=step){
				coords.add(UTMCoord.fromUTM(referenceCoord.getZone(), this.topRight.getHemisphere(), this.topRight.getEasting(), northings, globe));
			}
			positions = UTMListToPositionList(coords);
			polyline = createLineRenderable(positions, Polyline.LINEAR);
			lineSector = Sector.fromDegrees(this.bottomRight.getLatitude().getDegrees(), this.topRight.getLatitude().getDegrees(), this.topRight.getLongitude().getDegrees(), this.bottomRight.getLongitude().getDegrees());
			GridElement east = new GridElement(lineSector, polyline, GridElement.TYPE_LINE_EAST);
			east.value = this.bottomRight.getEasting();
			this.gridElements.add(east);

			// bottom parallel segment
			positions.clear();
			coords.clear();
			for(double eastings = this.bottomLeft.getEasting(); eastings <= this.bottomRight.getEasting(); eastings+=step){
				coords.add(UTMCoord.fromUTM(referenceCoord.getZone(), this.bottomLeft.getHemisphere(), eastings, this.bottomLeft.getNorthing(), globe));
			}
			positions = UTMListToPositionList(coords);
			polyline = createLineRenderable(positions, Polyline.LINEAR);
			lineSector = Sector.fromDegrees(this.bottomLeft.getLatitude().getDegrees(), this.bottomRight.getLatitude().getDegrees(), this.bottomLeft.getLongitude().getDegrees(), this.bottomRight.getLongitude().getDegrees());
			GridElement south = new GridElement(lineSector, polyline, GridElement.TYPE_LINE_SOUTH);
			south.value = this.bottomLeft.getNorthing();
			this.gridElements.add(south);

			// top parallel segment
			positions.clear();
			coords.clear();
			for(double eastings = this.topLeft.getEasting(); eastings <= this.topRight.getEasting(); eastings+=step){
				coords.add(UTMCoord.fromUTM(referenceCoord.getZone(), this.topLeft.getHemisphere(), eastings, this.topLeft.getNorthing(), globe));
			}
			positions = UTMListToPositionList(coords);
			polyline = createLineRenderable(positions, Polyline.LINEAR);
			lineSector = Sector.fromDegrees(this.topRight.getLatitude().getDegrees(), this.topLeft.getLatitude().getDegrees(), this.topLeft.getLongitude().getDegrees(), this.topRight.getLongitude().getDegrees());
			GridElement north = new GridElement(lineSector, polyline, GridElement.TYPE_LINE_NORTH);
			north.value = this.topLeft.getNorthing();
			this.gridElements.add(north);
		}
	}

	/**
	 * This class defines the Latitude bands that are used in UTM/MGRS implementations. These bands tiles are rendered at the highest level, and at varying latitude degrees, but are otherwise
	 * very similar to an ExtendedTile.
	 * 
	 * @author Senior Capstone
	 */
	class LatBandTile{
		private ArrayList<GridElement> gridElements;
		private String tileName;
		private Sector mainSector, crossDateLineSector;
		private UTMCoord topLeft, topRight, bottomLeft, bottomRight;
		private String hemisphere;

		public LatBandTile(double maxLat, double minLat){
			this.tileName = calculateLabel(minLat);

			this.hemisphere = (minLat >= 0) ? AVKey.NORTH : AVKey.SOUTH;
			UTMCoord topCenter = UTMCoord.fromLatLon(Angle.fromDegrees(maxLat), referenceCoord.getCentralMeridian());
			UTMCoord bottomCenter = UTMCoord.fromLatLon(Angle.fromDegrees(minLat), referenceCoord.getCentralMeridian());
			//adjust for the change at the equator
			if(topCenter.getNorthing()>0){
				this.topLeft = UTMCoord.fromUTM(referenceCoord.getZone(), this.hemisphere, 0, topCenter.getNorthing());
				this.topRight = UTMCoord.fromUTM(referenceCoord.getZone(), this.hemisphere, 1000000, topCenter.getNorthing());
			}
			else{
				this.topLeft = UTMCoord.fromUTM(referenceCoord.getZone(), this.hemisphere, 0, 10000000);
				this.topRight = UTMCoord.fromUTM(referenceCoord.getZone(), this.hemisphere, 1000000, 10000000);
			}
			this.bottomLeft = UTMCoord.fromUTM(referenceCoord.getZone(), this.hemisphere, 0, bottomCenter.getNorthing());
			this.bottomRight = UTMCoord.fromUTM(referenceCoord.getZone(), this.hemisphere, 1000000, bottomCenter.getNorthing());

			Sector sector = Sector.fromUTMRectangle(referenceCoord.getZone(), 
					bottomLeft.getHemisphere(), 
					bottomLeft.getEasting(), 
					bottomRight.getEasting(), 
					bottomLeft.getNorthing(), 
					topLeft.getNorthing());

			this.mainSector = new Sector(sector);

			//this is for handling sectors that cross the date line
			if(sector.getMinLongitude().getDegrees() < 0 && sector.getMaxLongitude().getDegrees() > 0){
				this.mainSector = new Sector(sector.getMinLatitude(), 
						sector.getMaxLatitude(),
						sector.getMaxLongitude(),
						Angle.POS180);
				this.crossDateLineSector = new Sector(sector.getMinLatitude(),
						sector.getMaxLatitude(),
						Angle.NEG180,
						sector.getMinLongitude());
			}
		}
		private String calculateLabel(double minLat){
			switch((int) minLat){
			case 72: return referenceCoord.getZone() + "X";
			case 64: return referenceCoord.getZone() + "W";
			case 56: return referenceCoord.getZone() + "V";
			case 48: return referenceCoord.getZone() + "U";
			case 40: return referenceCoord.getZone() + "T";
			case 32: return referenceCoord.getZone() + "S";
			case 24: return referenceCoord.getZone() + "R";
			case 16: return referenceCoord.getZone() + "Q";
			case 8: return referenceCoord.getZone() + "P";
			case 0: return referenceCoord.getZone() + "N";
			case -8: return referenceCoord.getZone() + "M";
			case -16: return referenceCoord.getZone() + "L";
			case -24: return referenceCoord.getZone() + "K";
			case -32: return referenceCoord.getZone() + "J";
			case -40: return referenceCoord.getZone() + "H";
			case -48: return referenceCoord.getZone() + "G";
			case -56: return referenceCoord.getZone() + "F";
			case -64: return referenceCoord.getZone() + "E";
			case -72: return referenceCoord.getZone() + "D";
			default: return referenceCoord.getZone() + "C";
			}
		}
		public boolean isInView(DrawContext dc){
			//this is for handling frustum intersections with sectors that cross the date line
			if(!viewFrustum.intersects(this.getExtent(dc.getGlobe(), dc.getVerticalExaggeration()))){
				if(this.crossDateLineSector != null && viewFrustum.intersects(this.getDateLineExtent(dc.getGlobe(), dc.getVerticalExaggeration())))
					return true;
				else
					return false;
			}
			return true;
		}
		public void selectRenderables(DrawContext dc){
			if(this.gridElements == null)
				createRenderables(dc);
			for(GridElement ge : this.gridElements){
				addRenderable(ge.renderable, GRATICULE_LATBAND);
			}
		}
		public void clearRenderables(){
			if(this.gridElements != null){
				this.gridElements.clear();
				this.gridElements = null;
			}
		}

		public Extent getExtent(Globe globe, double ve){
			double centerElevation = globe.getElevation(this.mainSector.getCentroid().getLatitude(), this.mainSector.getCentroid().getLongitude());
			return Sector.computeBoundingCylinder(globe, ve, this.mainSector, -5000, centerElevation);
		}

		public Extent getDateLineExtent(Globe globe, double ve){
			double centerElevation = globe.getElevation(this.crossDateLineSector.getCentroid().getLatitude(), this.crossDateLineSector.getCentroid().getLongitude());
			return Sector.computeBoundingCylinderOrig(globe, ve, this.crossDateLineSector, -5000, centerElevation);
		}

		private ArrayList<Position> UTMListToPositionList(Iterable<? extends UTMCoord> coords){
			ArrayList<Position> ret = new ArrayList<Position>();
			for(UTMCoord utm : coords){
				ret.add(Position.fromDegrees(utm.getLatitude().getDegrees(), utm.getLongitude().getDegrees()));
			}
			return ret;
		}
		private void createRenderables(DrawContext dc){
			this.gridElements = new ArrayList<GridElement>();
			ArrayList<UTMCoord> coords = new ArrayList<UTMCoord>();
			ArrayList<Position> positions = new ArrayList<Position>();
			Globe globe = WorldWindCanvas.getWorldWindModel().getGlobe();

			double northingStep = (this.topLeft.getNorthing() - this.bottomLeft.getNorthing())/100 - 1;
			double eastingStep = 1000000/10;
			// left meridian segment
			positions.clear();
			coords.clear();
			for(double northings = this.topLeft.getNorthing(); northings >= this.bottomLeft.getNorthing(); northings-=northingStep){
				coords.add(UTMCoord.fromUTM(referenceCoord.getZone(), this.topLeft.getHemisphere(), this.topLeft.getEasting(), northings, globe));
			}
			positions = UTMListToPositionList(coords);
			Object polyline = createLineRenderable(positions, Polyline.LINEAR);
			Sector lineSector = Sector.fromDegrees(this.bottomLeft.getLatitude().getDegrees(), this.topLeft.getLatitude().getDegrees(), this.topLeft.getLongitude().getDegrees(), this.bottomLeft.getLongitude().getDegrees());
			GridElement west = new GridElement(lineSector, polyline, GridElement.TYPE_LINE_WEST);
			west.value = this.bottomLeft.getEasting();
			this.gridElements.add(west);

			// right meridian segment
			positions.clear();
			coords.clear();
			for(double northings = this.topRight.getNorthing(); northings >= this.bottomRight.getNorthing(); northings-=northingStep){
				coords.add(UTMCoord.fromUTM(referenceCoord.getZone(), this.topRight.getHemisphere(), this.topRight.getEasting(), northings, globe));
			}
			positions = UTMListToPositionList(coords);
			polyline = createLineRenderable(positions, Polyline.LINEAR);
			lineSector = Sector.fromDegrees(this.bottomRight.getLatitude().getDegrees(), this.topRight.getLatitude().getDegrees(), this.topRight.getLongitude().getDegrees(), this.bottomRight.getLongitude().getDegrees());
			GridElement east = new GridElement(lineSector, polyline, GridElement.TYPE_LINE_EAST);
			east.value = this.bottomRight.getEasting();
			this.gridElements.add(east);

			// bottom parallel segment
			positions.clear();
			coords.clear();
			for(double eastings = this.bottomLeft.getEasting(); eastings <= this.bottomRight.getEasting(); eastings+=eastingStep){
				coords.add(UTMCoord.fromUTM(referenceCoord.getZone(), this.bottomLeft.getHemisphere(), eastings, this.bottomLeft.getNorthing(), globe));
			}
			positions = UTMListToPositionList(coords);
			polyline = createLineRenderable(positions, Polyline.LINEAR);
			lineSector = Sector.fromDegrees(this.bottomLeft.getLatitude().getDegrees(), this.bottomRight.getLatitude().getDegrees(), this.bottomLeft.getLongitude().getDegrees(), this.bottomRight.getLongitude().getDegrees());
			GridElement south = new GridElement(lineSector, polyline, GridElement.TYPE_LINE_SOUTH);
			south.value = this.bottomLeft.getNorthing();
			this.gridElements.add(south);

			// top parallel segment
			positions.clear();
			coords.clear();
			for(double eastings = this.topLeft.getEasting(); eastings <= this.topRight.getEasting(); eastings+=eastingStep){
				coords.add(UTMCoord.fromUTM(referenceCoord.getZone(), this.topLeft.getHemisphere(), eastings, this.topLeft.getNorthing(), globe));
			}
			positions = UTMListToPositionList(coords);
			polyline = createLineRenderable(positions, Polyline.LINEAR);
			lineSector = Sector.fromDegrees(this.topRight.getLatitude().getDegrees(), this.topLeft.getLatitude().getDegrees(), this.topLeft.getLongitude().getDegrees(), this.topRight.getLongitude().getDegrees());
			GridElement north = new GridElement(lineSector, polyline, GridElement.TYPE_LINE_NORTH);
			north.value = this.topLeft.getNorthing();
			this.gridElements.add(north);

			UTMCoord center = UTMCoord.fromUTM(referenceCoord.getZone(), 
					this.bottomLeft.getHemisphere(), 
					5e5, 
					((this.bottomLeft.getNorthing() + this.topLeft.getNorthing())/2));
			GeographicText text = new UserFacingText(this.tileName, new Position(center.getLatitude(), center.getLongitude(), 0));
			text.setPriority(10e6);
			this.gridElements.add(new GridElement(this.mainSector, text, GridElement.TYPE_GRIDZONE_LABEL));
		}
	}

}